<?php
/**
 * @User: sethink
 */

namespace bangtech\swooleOrm\db;

use bangtech\swooleOrm\MysqlPool;
use chan;
use Exception;
use RuntimeException;
use Swoole\Coroutine\Mysql;

class Query
{
    /**
     * Mysql连接池
     * @var MysqlPool
     */
    protected $MysqlPool;

    /**
     * sql生成器
     * @var Builder
     */
    protected $builder;

    //db参数
    protected $options = [
        'table' => '',
        'alias' => '',
        'where' => [],
        'whereNum' => 0,
        'field' => '*',
        'order' => [],
        'distinct' => false,
        'join' => [],
        'union' => '',
        'group' => '',
        'having' => '',
        'limit' => '',
        'page' => '',
        'lock' => false,
        'fetch_sql' => false,
        'data' => [],
        'prefix' => '',
        'setDefer' => true
    ];


    public function __construct()
    {
        // 创建Builder对象
        $this->builder = new Builder();
    }

    /**
     * @初始化
     *
     * @param $MysqlPool
     * @return $this
     */
    public function init(MysqlPool $MysqlPool): Query
    {
        $this->MysqlPool = $MysqlPool;
        $this->options['prefix'] = $MysqlPool->config['prefix'];
        $this->options['setDefer'] = $MysqlPool->config['setDefer'];
        return $this;
    }


    /**
     * @表名
     *
     * @param $tableName
     * @return $this
     */
    public function name($tableName = ''): Query
    {
        $this->options['table'] = $this->options['prefix'] . $tableName;
        return $this;
    }

    /**
     * @param string $tableName
     * @return $this
     */
    public function table($tableName = ''): Query
    {
        $this->options['table'] =  $tableName;
        return $this;
    }

    /**
     * 指定数据表别名
     * @access public
     * @param array|string $alias 数据表别名
     * @return $this
     */
    public function alias($alias): Query
    {
        $this->options['alias'] = $alias;
        return $this;
    }


    /**
     * @查询字段
     *
     * @param string $field
     * @return $this
     */
    public function field($field = ''): Query
    {
        if (empty($field)) {
            return $this;
        }
        $field_array = explode(',', $field);
        //去重
        $this->options['field'] = array_unique($field_array);
        return $this;
    }

    public function count(string $field = '*')
    {
        if (preg_match("/^(\w+)$/",$field)){
            $this->options['field'] = "COUNT(`{$field}`) AS `table_count`";
        }else{
            $this->options['field'] = 'COUNT(*) AS `table_count`';
        }

        $result = $this->builder->select($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }
        $result = $this->query($result);
        if (isset($result[0]['table_count'])){
            return (int)$result[0]['table_count'];
        }else{
            return 0;
        }

    }


    /**
     * @order by
     *
     * @param array $order
     * @return $this
     */
    public function order($order = []): Query
    {
        $this->options['order'] = $order;
        return $this;
    }


    /**
     * @group by
     *
     * @param string $group
     * @return $this
     */
    public function group($group = ''): Query
    {
        $this->options['group'] = $group;
        return $this;
    }


    /**
     * @having
     *
     * @param string $having
     * @return $this
     */
    public function having($having = ''): Query
    {
        $this->options['having'] = $having;
        return $this;
    }


    //暂未实现
    public function join($join, string $condition = null, string $type = 'INNER')
    {
        $table = $this->parseJoinSql($join);
        $condition = $this->parseJoinSql($condition);
        $this->options['join'][] = [$table, strtoupper($type), $condition];
        return $this;
    }

    protected function parseJoinSql($sql)
    {
        if (preg_match_all("/\_\_(\w+)\_\_/",$sql,$matches,PREG_SET_ORDER)){
            $from = array_column($matches,0);
            $to   = array_column($matches,1);
            foreach ($to as &$value) {
                $value = $this->options['alias'].$value;
            }
            return strtr($sql,$from,$to);
        }

        return $sql;
    }

    /**
     * 指定查询数量
     * @access public
     * @param int $offset 起始位置
     * @param int $length 查询数量
     * @return $this
     */
    public function limit(int $offset, int $length = null)
    {
        $this->options['limit'] = $offset . ($length ? ',' . $length : '');

        return $this;
    }

    /**
     * 指定分页
     * @access public
     * @param int $page     页数
     * @param int $listRows 每页数量
     * @return $this
     */
    public function page(int $page, int $listRows = null)
    {
        $this->options['limit'] = ($page-1) * $listRows . ($listRows ? ',' . $listRows : '');

        return $this;
    }


    public function buildSql()
    {
        return $this->fetchSql()->select();
    }

    /**
     * @distinct
     * @param $distinct
     * @return $this
     */
    public function distinct($distinct): Query
    {
        $this->options['distinct'] = $distinct;
        return $this;
    }


    /**
     * @获取sql语句
     *
     * @return $this
     */
    public function fetchSql(): Query
    {
        $this->options['fetch_sql'] = true;
        return $this;
    }


    /**
     * @where语句
     * @param $field
     * @param null $op
     * @param null $condition
     * @param string $type
     * @return $this
     */
    public function where($field, $op = null, $condition = null, $type = 'AND'): Query
    {
        if (is_array($field)) {
            $whereArray = $field;
        } elseif (is_callable($field)){
            $field($this);
            return $this;
        } else {
            if ($op !== null && $condition === null){
                $condition = $op;
                $op = '=';
            }

            $whereArray = [
                $field => [($op !== null ? $op : '='), $condition, $type]
            ];
        }

        $this->options['where'][$this->options['whereNum']] = $whereArray;
        $this->options['whereNum']++;
        return $this;
    }

    /**
     * @param $field
     * @param null $op
     * @param null $condition
     * @return $this
     */
    public function whereOr($field, $op = null, $condition = null): Query
    {
        return $this->where($field,$op,$condition,'OR');
    }

    /**
     * @param $field
     * @param null $op
     * @param null $condition
     * @return $this
     */
    public function whereAnd($field, $op = null, $condition = null): Query
    {
        return $this->where($field,$op,$condition,'AND');
    }

    /**
     * @param $field
     * @param null $op
     * @param null $condition
     * @return $this
     */
    public function whereLike($field, $op = null, $condition = null): Query
    {
        return $this->where($field,$op,$condition,'LIKE');
    }


    /**
     * @lock加锁
     *
     * @param bool $lock
     * @return $this
     */
    public function lock($lock = false): Query
    {
        $this->options['lock'] = $lock;
        return $this;
    }


    /**
     * @设置是否返回结果
     *
     * @param bool $bool
     * @return $this
     */
    public function setDefer(bool $bool = true): Query
    {
        $this->options['setDefer'] = $bool;
        return $this;
    }


    /**
     * @查询一条数据
     *
     * @return array|mixed
     * @throws Exception
     */
    public function find()
    {
        $this->options['limit'] = 1;

        $result = $this->builder->select($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }
        $result = $this->query($result);
        return $result[0];
    }


    /**
     * @查询
     *
     * @return bool|mixed
     * @throws Exception
     */
    public function select()
    {
        // 生成查询SQL
        $result = $this->builder->select($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }

        return $this->query($result);
    }


    /**
     * 添加
     * @param array $data
     * @return mixed|string
     * @throws Exception
     */
    public function insert($data = [])
    {
        $this->options['data'] = $data;

        $result = $this->builder->insert($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }
        return $this->query($result);
    }

    /**
     * @param array $data
     * @return mixed|string|null
     * @throws Exception
     */
    public function insertGetId(array $data = [])
    {
        return $this->insert($data);
    }

    /**
     * @param array $data
     * @return mixed|null
     * @throws Exception
     */
    public function insertAll($data = [])
    {
        $this->options['data'] = $data;

        $result = $this->builder->insertAll($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }
        return $this->query($result);
    }


    /**
     * @param array $data
     * @return mixed|null
     * @throws Exception
     */
    public function update($data = [])
    {
        $this->options['data'] = $data;

        $result = $this->builder->update($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }
        return $this->query($result);
    }


    /**
     * @return mixed|null
     * @throws Exception
     */
    public function delete()
    {
        // 生成查询SQL
        $result = $this->builder->delete($this->options);

        if (!empty($this->options['fetch_sql'])) {
            return $this->getRealSql($result);
        }

        return $this->query($result);
    }


    /**
     * @获取连接
     *
     * @return mixed
     */
    public function instance()
    {
        return $this->MysqlPool->get();
    }


    /**
     * $入池
     *
     * @param $mysql
     */
    public function put($mysql)
    {
        if ($mysql instanceof Mysql) {
            $this->MysqlPool->put($mysql);
        } else {
            throw new RuntimeException('传入的$mysql不属于该连接池');
        }
    }


    /**
     * @执行sql
     * @param $result
     * @return mixed
     * @throws Exception
     */
    public function query($result)
    {
        $chan = new chan(1);

        go(function () use ($chan, $result) {
            try {
                $dumpError = false;

                $mysql = $this->MysqlPool->get();

                if (is_string($result)) {
                    $rs = $mysql->query($result);

                    if ($rs === false) {
                        $dumpError = true;
                    }

                    if ($this->options['setDefer']) {
                        $chan->push([0,$rs]);
                    }
                } else {
                    $stmt = $mysql->prepare($result['sql']);

                    if ($stmt === false) {
                        $dumpError = true;
                    }

                    if ($stmt) {
                        $rs = $stmt->execute($result['sethinkBind']);

                        if ($rs === false) {
                            $dumpError = true;
                        }

                        if ($this->options['setDefer']) {
                            if ($this->options['limit'] == 1) {
                                if (count($rs) > 0) {
                                    $chan->push([0,$rs[0]]);
                                } else {
                                    $chan->push([0,null]);
                                }
                            } else {
                                if (strstr($result['sql'], 'INSERT INTO')) {
                                    $chan->push([0,$mysql->insert_id]);
                                } else {
                                    $chan->push([0,$rs]);
                                }
                            }
                        }
                    }
                }
                $this->put($mysql);

                if ($dumpError) {
                    $chan->push([1,[$mysql->error,$mysql->errno,new Exception($mysql->error,$mysql->errno)]]);
                }
            } catch (Exception $e) {
                if ($this->options['setDefer']) {
                    $chan->push([0,null]);
                }else{
                    $chan->push([1,[$e->getMessage(),$e->getCode(),$e->getPrevious()]]);
                }
            }

        });

        if ($this->options['setDefer']) {
            $res = $chan->pop();
            if ($res[0] === 0){
                return $res[1];
            }else{
                throw new Exception($res[1][0],$res[1][1],$res[1][2]);
            }

        }
        return null;
    }

    /**
     * @sql语句
     *
     * @param $result
     * @return mixed
     */
    protected function getRealSql($result)
    {
        if (count($result['sethinkBind']) > 0) {
            foreach ($result['sethinkBind'] as $v) {
                $result['sql'] = substr_replace($result['sql'], "'{$v}'", strpos($result['sql'], '?'), 1);
            }
        }

        return $result['sql'];
    }


    public function __destruct()
    {
        unset($this->MysqlPool);
        unset($this->builder);
        unset($this->options);
    }


}
